package com.example.fileupload.storage.impl;

import com.example.fileupload.storage.AbstractStorageStrategy;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * MinIO存储策略实现 - 函数式编程风格
 */
@Slf4j
@Component
@ConditionalOnProperty(name = "storage.minio.enabled", havingValue = "true")
public class MinioStorageStrategy extends AbstractStorageStrategy {
    
    @Value("${storage.minio.endpoint}")
    private String endpoint;
    
    @Value("${storage.minio.access-key}")
    private String accessKey;
    
    @Value("${storage.minio.secret-key}")
    private String secretKey;
    
    @Value("${storage.minio.bucket-name}")
    private String bucketName;
    
    @Value("${storage.minio.url-expiry:7}")
    private int urlExpiryDays;
    
    private MinioClient minioClient;
    
    @PostConstruct
    public void init() {
        this.minioClient = MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
        
        // 确保bucket存在
        ensureBucketExists()
                .ifPresentOrElse(
                        created -> log.info("MinIO存储策略初始化完成, bucket: {}", bucketName),
                        () -> log.error("MinIO存储策略初始化失败")
                );
    }
    
    @Override
    public StorageType getStorageType() {
        return StorageType.MINIO;
    }
    
    @Override
    protected StorageResult doStoreChunk(String chunkPath, InputStream inputStream, long size) {
        try {
            // 上传对象到MinIO
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(chunkPath)
                    .stream(inputStream, size, -1)
                    .build();
            
            ObjectWriteResponse response = minioClient.putObject(args);
            log.debug("MinIO分片上传成功: {}", chunkPath);
            
            return StorageResult.success(
                    chunkPath,
                    generateObjectUrl(chunkPath),
                    size
            );
            
        } catch (Exception e) {
            log.error("MinIO存储分片失败: {}", e.getMessage());
            return StorageResult.failure("MinIO存储分片失败: " + e.getMessage());
        }
    }
    
    @Override
    protected StorageResult doMergeChunks(String finalPath, List<String> chunkPaths, long totalSize) {
        try {
            // 从MinIO下载所有分片并合并
            ByteArrayOutputStream mergedStream = new ByteArrayOutputStream();
            
            chunkPaths.stream()
                    .forEach(chunkPath -> {
                        try {
                            GetObjectArgs args = GetObjectArgs.builder()
                                    .bucket(bucketName)
                                    .object(chunkPath)
                                    .build();
                            
                            try (InputStream chunkStream = minioClient.getObject(args)) {
                                chunkStream.transferTo(mergedStream);
                                log.debug("下载并合并MinIO分片: {}", chunkPath);
                            }
                        } catch (Exception e) {
                            throw new RuntimeException("合并分片失败: " + chunkPath, e);
                        }
                    });
            
            // 上传合并后的文件
            byte[] mergedData = mergedStream.toByteArray();
            PutObjectArgs putArgs = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(finalPath)
                    .stream(new ByteArrayInputStream(mergedData), mergedData.length, -1)
                    .build();
            
            minioClient.putObject(putArgs);
            log.info("MinIO文件合并完成: {}, 总大小: {}", finalPath, totalSize);
            
            return StorageResult.success(
                    finalPath,
                    generateObjectUrl(finalPath),
                    totalSize
            );
            
        } catch (Exception e) {
            log.error("MinIO合并文件失败: {}", e.getMessage());
            return StorageResult.failure("MinIO合并文件失败: " + e.getMessage());
        }
    }
    
    @Override
    protected boolean doDeleteFile(String path) {
        try {
            RemoveObjectArgs args = RemoveObjectArgs.builder()
                    .bucket(bucketName)
                    .object(path)
                    .build();
            
            minioClient.removeObject(args);
            log.debug("删除MinIO对象: {}", path);
            return true;
            
        } catch (Exception e) {
            log.error("删除MinIO对象失败: {}, 错误: {}", path, e.getMessage());
            return false;
        }
    }
    
    @Override
    public boolean exists(String path) {
        try {
            StatObjectArgs args = StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(path)
                    .build();
            
            minioClient.statObject(args);
            return true;
            
        } catch (Exception e) {
            return false;
        }
    }
    
    @Override
    public String getDownloadUrl(String path) {
        try {
            GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucketName)
                    .object(path)
                    .expiry(urlExpiryDays, java.util.concurrent.TimeUnit.DAYS)
                    .build();
            
            return minioClient.getPresignedObjectUrl(args);
            
        } catch (Exception e) {
            log.error("获取MinIO下载URL失败: {}", e.getMessage());
            throw new RuntimeException("获取MinIO下载URL失败: " + e.getMessage());
        }
    }
    
    // ============ 私有辅助方法 ============
    
    /**
     * 确保bucket存在
     */
    private Optional<Boolean> ensureBucketExists() {
        try {
            BucketExistsArgs existsArgs = BucketExistsArgs.builder()
                    .bucket(bucketName)
                    .build();
            
            if (!minioClient.bucketExists(existsArgs)) {
                MakeBucketArgs makeArgs = MakeBucketArgs.builder()
                        .bucket(bucketName)
                        .build();
                
                minioClient.makeBucket(makeArgs);
                log.info("创建MinIO bucket: {}", bucketName);
            }
            
            return Optional.of(true);
            
        } catch (Exception e) {
            log.error("确保MinIO bucket存在失败: {}", e.getMessage());
            return Optional.empty();
        }
    }
    
    /**
     * 生成对象URL
     */
    private String generateObjectUrl(String objectName) {
        return String.format("%s/%s/%s", endpoint, bucketName, objectName);
    }
    
    /**
     * 列出分片对象
     */
    private List<String> listChunkObjects(String md5) {
        ArrayList<String> re = new ArrayList<>();
        try {
            String prefix = "chunks/" + md5 + "/";
            ListObjectsArgs args = ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .prefix(prefix)
                    .build();

            minioClient.listObjects(args)
                    .forEach(result -> {
                        try {
                            Item item = result.get();
                            String simpleName = item.getClass().getSimpleName();
                            re.add(simpleName);
                        } catch (Exception e) {
                            throw new RuntimeException("获取对象名失败", e);
                        }
                    });
            return re;
        } catch (Exception e) {
            log.error("列出MinIO分片对象失败: {}", e.getMessage());
            return List.of();
        }
    }
    
    /**
     * 批量删除分片
     */
    public boolean deleteChunks(List<String> objectNames) {
        try {
            if (objectNames.isEmpty()) {
                return true;
            }
            
            List<DeleteObject> objects = objectNames.stream()
                    .map(DeleteObject::new)
                    .collect(Collectors.toList());
            
            RemoveObjectsArgs args = RemoveObjectsArgs.builder()
                    .bucket(bucketName)
                    .objects(objects)
                    .build();
            
            Iterable<io.minio.Result<DeleteError>> results = minioClient.removeObjects(args);
            
            // 检查删除结果
            for (io.minio.Result<DeleteError> result : results) {
                DeleteError error = result.get();
                if (error != null) {
                    log.warn("删除MinIO对象失败: {}, 错误: {}", error.objectName(), error.message());
                    return false;
                }
            }
            
            log.debug("成功删除MinIO对象: {}", objectNames.size());
            return true;
            
        } catch (Exception e) {
            log.error("批量删除MinIO对象失败: {}", e.getMessage());
            return false;
        }
    }
    
    // ============ 钩子方法重写 ============
    
    @Override
    protected Optional<StorageResult> postMergeChunks(StorageResult result) {
        // 合并完成后，异步删除分片
        cleanupChunksAsync(result.getPath());
        return Optional.of(result);
    }
    
    /**
     * 异步清理分片文件
     */
    private void cleanupChunksAsync(String finalPath) {
        CompletableFuture.runAsync(() -> {
            extractMd5FromPath(finalPath)
                    .ifPresent(md5 -> {
                        List<String> chunkObjects = listChunkObjects(md5);
                        boolean deleted = deleteChunks(chunkObjects);
                        if (deleted) {
                            log.debug("异步清理MinIO分片完成: {}", chunkObjects.size());
                        } else {
                            log.warn("异步清理MinIO分片失败");
                        }
                    });
        }).exceptionally(throwable -> {
            log.warn("异步清理MinIO分片失败: {}", throwable.getMessage());
            return null;
        });
    }
    
    /**
     * 从文件路径提取MD5
     */
    private Optional<String> extractMd5FromPath(String path) {
        try {
            String[] parts = path.split("/");
            return parts.length >= 2 ? 
                    Optional.of(parts[parts.length - 2]) : 
                    Optional.empty();
        } catch (Exception e) {
            return Optional.empty();
        }
    }
} 